package com.dman.multi.proxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;

public class HTTPProxyHandler implements ProxyHandler {
	
	private static final String HTTP_CONNECT = "CONNECT ";
	private static final String HTTP_CONNECTION = "Connection: Close ";
	private static final String HTTP_PROXY_AUTH = "Proxy-Authorization: ";
	private static final String HTTP_CHARSET = "US-ASCII";
	private static final String HTTP_LINE_TERMINATOR = "\r\n";
	private static final String HTTP_VERSION11 = "HTTP/1.1";
	private static final String HTTP_VERSION10 = "HTTP/1.0";
	private static final int RANDOM_PORT = 65533;
	
	private Map<ProxyAdaptor, Map<String, Boolean>> proxyProtocolConnectable = new HashMap<ProxyAdaptor, Map<String,Boolean>>();
		
	@Override
	public boolean connect(SocketChannel sockChannel, ProxyAdaptor proxyAuth,
			URL url) throws IOException {
		Map<String, Boolean> protocolConnectable = proxyProtocolConnectable.get(proxyAuth);
		boolean connectable = protocolConnectable.get(url.getProtocol());
		boolean connected = sockChannel.connect(proxyAuth.getProxy().address());
		if(connectable)
			sendRequest(sockChannel, url, proxyAuth);
		return connected;
	}
	
	@Override
	public String identifyProtocol(URL url, ProxyAdaptor proxyAuth)
			throws IOException {
		String proto = "HTTP";
		SocketChannel channel = SocketChannel.open();
		sendRequest(channel, url, proxyAuth);
		String resp = recvResponse(channel);
		if(isNativeProtocolSupported(resp)){
			if(url.getProtocol().equalsIgnoreCase("ftp")){
				channel = SocketChannel.open();
				testConnect(url.getHost(), RANDOM_PORT, proxyAuth.getAuth(), 
						proxyAuth.getProxy().address(), channel);
				resp = recvResponse(channel);
				if(isNativeProtocolSupported(resp))
					proto = url.getProtocol();
			}
		}
		
		// save the proxy-protocol relation to a map
		Map<String, Boolean> protocolConnectable = proxyProtocolConnectable.get(proxyAuth);
		if(protocolConnectable == null){
			protocolConnectable = new HashMap<String, Boolean>();
		}
		protocolConnectable.put(url.getProtocol(), url.getProtocol().equalsIgnoreCase(proto));
		proxyProtocolConnectable.put(proxyAuth, protocolConnectable);
		
		return proto;
	}
	
	public boolean isNativeProtocolSupported(String resp){
		String status = "";
		boolean supported = true;
		if(resp.startsWith(HTTP_VERSION11))
			status = resp.substring(HTTP_VERSION11.length());
		if(status.isEmpty() && resp.startsWith(HTTP_VERSION10))
			status = resp.substring(HTTP_VERSION10.length()).trim();
		
		if(status.startsWith("4"))
			supported = false;
		
		return supported;
	}
	
	public int sendRequest(SocketChannel sc, URL url, ProxyAdaptor proxyAuth) throws IOException{
		int port = url.getPort();
		if(port < 0)
			port = url.getDefaultPort();
		SocketAddress sa = proxyAuth.getProxy().address();
		return testConnect(url.getHost(), port, proxyAuth.getAuth(), sa, sc);
		
	}
	
	public int testConnect(String host, int port, String auth, SocketAddress sa, SocketChannel sc) throws IOException{
		String req = HTTP_CONNECT + host + ":" + port + " " + HTTP_VERSION11 
			+ HTTP_LINE_TERMINATOR + HTTP_PROXY_AUTH + auth +
			HTTP_LINE_TERMINATOR + HTTP_CONNECTION + HTTP_LINE_TERMINATOR + HTTP_LINE_TERMINATOR;
		if(!sc.isConnected())
			sc.connect(sa);
		return sc.write(ByteBuffer.wrap(req.getBytes(HTTP_CHARSET)));
	}
	
	public String recvResponse(SocketChannel sockChannel) throws IOException{
		String line = "";
		if(sockChannel.isConnected()){
			Socket s = sockChannel.socket();
			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
			line = br.readLine();
		}
		
		return line.toUpperCase();
	}
}
